// Copyright 2023 The Okteto Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package utils

import (
	"fmt"
	"path/filepath"
	"strings"

	"github.com/go-git/go-git/v5"
	oktetoLog "github.com/okteto/okteto/pkg/log"
	"github.com/okteto/okteto/pkg/repository"
)

// GetValidNameFromFolder returns name for a folder
// this name is not sanitized
func GetValidNameFromFolder(folder string) (string, error) {
	dir, err := filepath.Abs(folder)
	if err != nil {
		return "", fmt.Errorf("error inferring name: %w", err)
	}
	if folder == ".okteto" {
		dir = filepath.Dir(dir)
	}

	name := filepath.Base(dir)
	oktetoLog.Infof("autogenerated name: %s", name)
	return name, nil
}

// TranslateURLToName returns the repo name from the repository url
// removes .git in case it has this as suffix
// repoName is not sanitized
func TranslateURLToName(repoURL string) string {
	repoName := findRepoName(repoURL)

	if strings.HasSuffix(repoName, ".git") {
		repoName = repoName[:strings.LastIndex(repoName, ".git")]
	}
	return repoName
}

// findRepoName returns string after last "/" from repoURL
func findRepoName(repoURL string) string {
	possibleName := repoURL[strings.LastIndex(repoURL, "/")+1:]
	if possibleName == "" {
		possibleName = repoURL
		nthTrim := strings.Count(repoURL, "/")
		for i := 0; i < nthTrim-1; i++ {
			possibleName = possibleName[strings.Index(possibleName, "/")+1:]
		}
		possibleName = possibleName[:len(possibleName)-1]
	}
	return possibleName
}
func GetRepositoryURL(path string) (string, error) {
	repo, err := repository.FindTopLevelGitRepoFromPath(path)
	if err != nil {
		return "", fmt.Errorf("failed to analyze git repo: %w", err)
	}

	origin, err := repo.Remote("origin")
	if err != nil {
		if err != git.ErrRemoteNotFound {
			return "", fmt.Errorf("failed to get the git repo's remote configuration: %w", err)
		}
	}

	if origin != nil {
		return origin.Config().URLs[0], nil
	}

	remotes, err := repo.Remotes()
	if err != nil {
		return "", fmt.Errorf("failed to get git repo's remote information: %w", err)
	}

	if len(remotes) == 0 {
		return "", fmt.Errorf("git repo doesn't have any remote")
	}

	return remotes[0].Config().URLs[0], nil
}

type Graph map[string][]string

func GetDependentCyclic(g Graph) []string {
	visited := make(map[string]bool)
	stack := make(map[string]bool)
	cycle := make([]string, 0)

	// We need to iterate from all the nodes on the graph checking that there are no cycles in
	// the graph.
	for svcName := range g {
		// dfs search for all the possible paths of a graph, starting from a node. If we find more
		// than once one nodeit means that it has detected a cycle and returns the cycle
		if dfs(g, svcName, visited, stack) {
			for svc, isInStack := range stack {
				if isInStack {
					cycle = append(cycle, svc)
				}
			}
			return cycle
		}
	}
	return cycle
}

// dfs executes deep first search algorithm.
// More information can be found at https://en.wikipedia.org/wiki/Depth-first_search
func dfs(g Graph, svcName string, visited, stack map[string]bool) bool {
	isVisited := visited[svcName]
	if !isVisited {
		visited[svcName] = true
		stack[svcName] = true

		svc := g[svcName]
		for _, dependentSvc := range svc {
			if !visited[dependentSvc] && dfs(g, dependentSvc, visited, stack) {
				return true
			} else if value, ok := stack[dependentSvc]; ok && value {
				return true
			}
		}
	}
	stack[svcName] = false
	return false
}
